//
// Copyright (c) 2010 Luís Reis
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;

namespace Pine.Core
{
	public class ValueList : Value, IEquatable<ValueList>
	{
		private IDictionary<Value, Value> values;
		private Function CopyWrapper, EachWrapper, FindWrapper, KeysWrapper, LastWrapper, LengthWrapper, PopWrapper, FirstWrapper;
		
		public ICollection<Value> Keys
		{
			get {
				return values.Keys;
			}
		}
		
		public ValueList () : this(default(ValueList))
		{
		}
		
		public ValueList (ValueList other)  {
			if (other == null)
				values = new Dictionary<Value, Value>();
			else
				values = new Dictionary<Value, Value>(other.values);
			
			CopyWrapper = new Function(null, Copy);
			EachWrapper = new Function(null, Each);
			FindWrapper = new Function(null, Find);
			KeysWrapper = new Function(null, GetKeys);
			LastWrapper = new Function(null, Last);
			LengthWrapper = new Function(null, Length);
			PopWrapper = new Function(null, Pop);
			FirstWrapper = new Function(null, First);
		}
		
		public ValueList (IList<Value> values) : this() {
			
			for (int i = 0; i < values.Count; ++i) {
				this.values[new Number(i)] = values[i];
			}
		}
		
		public override string ToString ()
		{
			StringBuilder str = new StringBuilder();
			str.Append("[");
			
			bool first = true;
			
			foreach (Value key in values.Keys) {
				if (first)
					first = false;
				else
					str.Append(", ");
				
				str.Append(key.ToString());
				str.Append(" => ");
				str.Append(values[key].ToString());
			}
			
			str.Append("]");
			return str.ToString();
		}
		
		public Value Pop(Value key) {
			Value v = this.GetArrayElement(key);
			this.SetArrayElement(key, NullValue.Null);
			return v;
		}
		
		public void Clear()
		{
			values.Clear();
		}
		
		public ValueList Copy()
		{
			ValueList nvalues = new ValueList();
			foreach (Value key in values.Keys)
			{
				nvalues.values[key] = values[key];
			}
			return nvalues;
		}
		
		public override Value GetArrayElement(Value key)
		{
			if (!values.ContainsKey(key)) {
				return NullValue.Null;
			}
			return values[key];	
		}
		
		public Value GetArrayElement(double key)
		{
			return GetArrayElement(new Number(key));
		}
		
		public override void SetArrayElement(Value key, Value value) {
			if (value is NullValue) {
				values.Remove(key);
			}
			else {
				values[key] = value;
			}
		}
		
		public int Size {
			get {
				return values.Keys.Count;
			}
		}
		
		public bool IsEmpty()
		{
			return values.Count == 0;
		}
		
		public override Value GetPineType() {
			return PineType.ArrayType;
		}
		
		public void Copy(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl");
			
			rl.Clear();
			rl.SetArrayElement(new Number(0), new ValueList(this));
		}
		
		public void Each(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl");
			
			Value apply = rl.GetArrayElement(0);
			rl.Clear();
			
			if (!apply.IsCallable) {
				rl.SetArrayElement(new StringValue("failure"), BooleanValue.True);
				return;
			}
			
			ValueList list = new ValueList(this);
			int idx = 0;
			Value lastReturn = NullValue.Null;
			Value interrupt = NullValue.Null;
			foreach (Value key in list.values.Keys)
			{
				Value value = list.GetArrayElement(key);
				
				rl.SetArrayElement(new StringValue("key"), key);
				rl.SetArrayElement(new StringValue("value"), value);
				rl.SetArrayElement(new Number(0), value);
				rl.SetArrayElement(new StringValue("index"), new Number(idx));
				
				apply.Invoke(rl);
				lastReturn = rl.GetArrayElement(0);
				interrupt = rl.GetArrayElement(new StringValue("interrupt"));
				rl.Clear();
				
				++idx;
				
				if (interrupt.ToBoolean())
					break;
			}
			
			rl.SetArrayElement(new Number(0), lastReturn);
			rl.SetArrayElement(new StringValue("failure"), BooleanValue.False);
		}
		
		public void Find(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl");
			
			Value element = rl.GetArrayElement(0);
			rl.Clear();
			
			if (element == NullValue.Null) {
				return;
			}
			
			foreach (Value key in values.Keys) {
				if (element.Equals(values[key])) {
					rl.SetArrayElement(new Number(0), key);
					return;
				}
			}
			
			return;
		}
		
		public void Last(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl");
			
			rl.Clear();
			if (values.Count == 0)
				return;
			
			var last = values.Last();
			
			rl.SetArrayElement(new Number(0), last.Value);
			rl.SetArrayElement(new StringValue("key"), last.Key);
		}
		
		public void First(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl");
			
			rl.Clear();
			if (values.Count == 0)
				return;
			
			var first = values.First();
			
			rl.SetArrayElement(new Number(0), first.Value);
			rl.SetArrayElement(new StringValue("key"), first.Key);
		}
		
		public void Length(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl");
			
			rl.Clear();
			rl.SetArrayElement(new Number(0), new Number(values.Count));
		}
		
		public void GetKeys(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl");
			
			rl.Clear();
			rl.SetArrayElement(new Number(0), new ValueList(new List<Value>(Keys)));
		}
		
		public void Pop(Function f, ValueList rl) {
			if (rl == null)
				throw new ArgumentNullException("rl");
			
			rl.Clear();
			if (values.Count == 0) {
				rl.SetArrayElement(new StringValue("failure"), BooleanValue.True);
				return;
			}
			
			var last = values.Last();
			values.Remove(last);
			
			rl.SetArrayElement(new Number(0), last.Value);
			rl.SetArrayElement(new StringValue("key"), last.Key);
			rl.SetArrayElement(new StringValue("failure"), BooleanValue.False);
		}
		
		public override Value FieldGet (string fieldname)
		{
			if (fieldname == "copy")
				return CopyWrapper;
			if (fieldname == "each")
				return EachWrapper;
			if (fieldname == "find")
				return FindWrapper;
			if (fieldname == "keys")
				return KeysWrapper;
			if (fieldname == "last")
				return LastWrapper;
			if (fieldname == "length")
				return LengthWrapper;
			if (fieldname == "pop")
				return PopWrapper;
			if (fieldname == "first")
				return FirstWrapper;
			
			throw new PineException("No such field in array: " + fieldname);
		}
		
		public override bool Equals (object obj)
		{
			return Equals(obj as ValueList);
		}
		
		public bool Equals (ValueList list)
		{
			if (list == null) return false;
			
			if (values.Count != list.values.Count) return false;
			
			foreach(var val in values) {
				if (!list.values.ContainsKey(val.Key))
					return false;
				
				if (!val.Value.Equals(list.values[val.Key]))
					return false;
			}
			
			return true;
		}
	}
}

